import numpy as np
import cv2
import matplotlib.pyplot as plt
import numbers

class Contours:
    def __init__(self):
        pass

    @staticmethod
    def getContours(image):
        '''
        Wrapper around openCV's cv2.findContours() function
        (see: http://docs.opencv.org/modules/imgproc/doc/
            structural_analysis_and_shape_descriptors.html#cv2.findContours)
        Sets convenient options and converts contours to a list of ndarrays.

        Parameter:
        ---------
            image: ndarray, input image

        Returns:
        --------
            contours: list, list of contours
        '''

        image = image.astype(np.uint8)                                          # astype:字段类型转换  #uint8:0~255,int8:-128~127
        if cv2.__version__ < '3.1.0':
            contours, hirachy = cv2.findContours(image, cv2.RETR_CCOMP,         # findContours(寻找轮廓的图像，轮廓的检索模式，轮廓的检索方法)
                                                cv2.CHAIN_APPROX_TC89_L1)       # 建立两个等级的轮廓，上面的一层为外边界，里面的一层为内孔的边界信息，使用teh-Chinl chain 近似算法
        else:                                                                   # findContours()函数首先返回一个list，list中每个元素都是图像中的一个轮廓，用numpy中的ndarray表示
            contours, hirachy = cv2.findContours(image, cv2.RETR_CCOMP,
                                                        cv2.CHAIN_APPROX_TC89_L1)
        # contours = np.asarray(contours)
        return contours
    

    @staticmethod
    def getFeatures(contours):
        dct = {'perimeter': [],
                'area':[],
                'center': [],
                'solidity':[],
                'orientation':[],
                'boundingRect':[],
                'majorAxis':[],
                'minorAxis':[],
                'enclosingCircle':[]}

        for c in contours:

            try:
                perimeter = cv2.arcLength(c, True)                              # arcLength(cnts,true)：轮廓周长（轮廓，闭环）
                area = cv2.contourArea(c, True)                                 # contourArea(cnts,true)：轮廓面积（轮廓，闭环）
            except:
                perimeter, area = 0,0
            
            try:
                M = cv2.moments(c)                                              # moments(c)：矩
                center = (int(M['m10']/M['m00']), int(M['m01']/M['m00']))       # center=(x,y)：重心
            except:
                center = (0,0)
            try:
                hull = cv2.convexHull(c)                                        # 图像的凸包
                hull_area = cv2.contourArea(hull)                               # 凸包面积
            except:
                hull_area = 0
            
            try:
                solidity = float(area)/hull_area                                # 图像紧实度
            except:
                solidity = 0

            dct['perimeter'].append(perimeter)
            dct['area'].append(area)

            dct['center'].append(center)
            dct['solidity'].append(solidity)

        return dct
 


    @staticmethod
    def flattenContours(raw_contours):
        '''
        Helper function for flattening contours consisting of nested ndarrays

        Parameter:
        ----------
            contours: list, list of ndarrays

        Returns:
        --------
            flattened_contours: list, list of flattened ndarrays
        '''

        converted_contours = []
        for contour in raw_contours:
            new_contour = []
            for point in contour:
                x, y = (float(point[0][0]), float(point[0][1]))
                new_point = [x, y]
                new_contour.append(new_point)
            converted_contours.append(new_contour)
        return converted_contours

    @staticmethod
    def thresholdContours(contours, threshold):                                 # 根据轮廓去除面积(面积降噪)
        '''
        Thresholds a given list of contours by length.

        Parameter:
        ---------
            contours: list, list of contours
            threshold: integer, length-threshold

        Returns:
        --------
            filtered_cont: list, list of contours
        '''

        thresholded_contours = []

        if isinstance(threshold, numbers.Number):
            threshold = [ threshold, 0 ]

        for c, i in zip(contours, range(len(contours))):
            if threshold[0] == 0 and threshold[1] == 0:
                thresholded_contours = contours
            elif threshold[0] == 0 and threshold[1] > 0:
                if (cv2.contourArea(c) < threshold[1]):
                    thresholded_contours.append(c)
            elif threshold[0] > 0 and threshold[1] == 0:
                if (cv2.contourArea(c) > threshold[0]):
                    thresholded_contours.append(c)
            elif threshold[0] > 0 and threshold[1] > 0:
                area = cv2.contourArea(c)
                if (area > threshold[0] and area < threshold[1]):
                    thresholded_contours.append(c)
        return thresholded_contours

    @staticmethod
    def fillContours(image, contours, color=[], path=''):
        '''
        轮廓填充

        Parameters
        ----------
        image : 2D numpy array
            待填充图像.
        contours : list
            轮廓数组组成的列表.
        color : TYPE, optional
            DESCRIPTION. The default is [].
        path : TYPE, optional
            DESCRIPTION. The default is ''.

        Returns
        -------
        filledimage : TYPE
            DESCRIPTION.

        '''
        conlen = len(contours)
        if len(image.shape)==3:
            channels = 3
        else:
            channels = 1

        if color == []:
            if channels == 3:
                colorList = [(255,255,255)]*conlen                              # 边界默认黑色
            else:
                colorList = [255]*conlen
        elif type(color) is tuple:                                              # color2list
            colorList = [color]*conlen
        elif type(color) is int:
            colorList = [color]*conlen
        elif type(color) is list:
            colorList = color
        for i in range(conlen):
            filledimage = cv2.drawContours(image, contours, i, 
                                           colorList[i], thickness=cv2.FILLED)  # drawContours(目标图像，目标轮廓数组，目标轮廓索引，颜色，绘图密度)
            
        return filledimage

    @staticmethod
    def sort(contours, feature='position', method='rightLeft'):
        	# initialize the reverse flag and sort index
        reverse = False
        i = 0
        # handle if we need to sort in reverse
        if method == "rightLeft" or method == "bottomTop":
            reverse = True

        # handle if we are sorting against the y-coordinate rather than
        # the x-coordinate of the bounding box
        if method == "leftRight" or method == "topBottom":
            i = 1

        # construct the list of bounding boxes and sort them from top to
        # bottom
        boundingBoxes = [cv2.boundingRect(c) for c in contours]                 # 对每个轮廓包矩形边框，返回x，y：矩阵左上点的坐标，w，h：矩阵的宽和高
        (cnts, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes),       # sorted(可迭代对象，比较函数，排序规则)
            key=lambda b:b[1][i], reverse=reverse))

        # return the list of sorted contours and bounding boxes
        return cnts

class Processing:
    def __init__(self, isDebug=False):
        self.isDebug = isDebug

    def averageImgWithMask(self, img, mask=None, flag=[1]):                     # 获取mask后的图像各自通道的数据均值（黑白、rgb通用）
        rtv = []
        # if there is no mask, create one according img
        if type(mask) is not np.ndarray:                                        # 未输入mask的话，默认设置一个关于全图像的mask
            try:
                channels = img.shape[2]
                maskV = [0]*channels
                mask = np.any(img!=maskV, axis=(-1))                            # 多通道，img像素的所有的通道都是0，这个像素的mask才是0，否则返回1
            except:
                maskV = 0
                mask = np.uint8(img!=0)                                         # 单通道，只要像素值是0，这个像素的mask就是0
            av = np.true_divide(img.sum((0,1)),mask.sum((0,1)))                 # 求各通道各自的平均值     #true_divide返回浮点数结果
            rtv.append(av)
            return rtv
        for f in flag:
            maskcp = mask.copy()
            maskcp[maskcp!=f]=0
            maskcp[maskcp==f]=1
            imgMasked = self.maskImage(img, maskcp)
            rtv.append(imgMasked.sum((0,1))/maskcp.sum())                       # 求各通道各自的平均值

        return rtv                                                              # 返回mask后的不同通道各自的图像均值，1维或n维数组，每行数组包含若干连通区域的均值
 

    def caliRGB(self, img, whiteRef):
        
        dst = np.float32(img)/np.float32(whiteRef)
        return dst

    def contoursFilter(self, img, thresh=[200, 0], color=1):                    # 面积降噪（可能用不到）
        contours = Contours.getContours(img)
        contoursFiltered = Contours.thresholdContours(contours, thresh)
        dst = np.zeros((img.shape[0],img.shape[1]), dtype=np.uint8)
        Contours.fillContours(dst, contoursFiltered, color=color)
        if self.isDebug:
            plt.imshow(dst)
            plt.title('debug: Processing contoursFilter')
            plt.show()

        return dst

    def contoursFeatures(self, img, contours, featureType=0):                   #获取轮廓的特征轮廓、颜色等（可能用不到）
        cnt = len(contours)
        if cnt > 255:
            print('ERRRRRR')
        if featureType == 1:
            features = Contours.getFeatures(contours)
        elif featureType == 2:
            color = list(range(1,cnt+1))
            mask = np.zeros((img.shape[0],img.shape[1]), dtype=np.uint16)
            Contours.fillContours(mask, contours, color=color)
            colors = self.averageImgWithMask(img, mask=mask, flag=color)
            features = {'color':colors}
        else:
            features = Contours.getFeatures(contours)
            color = list(range(1,cnt+1))
            mask = np.zeros((img.shape[0],img.shape[1]), dtype=np.uint16)
            Contours.fillContours(mask, contours, color=color)
            colors = self.averageImgWithMask(img, mask=mask, flag=color)
            features['color'] = list(colors)
            features['contourMask'] = mask
        return features

    def crop(self, img):
        # crpPst = [int(c) for c in crpPst]
        #print(crpPst)
        #dst = img[crpPst[1]:crpPst[1]+crpPst[3],crpPst[0]:crpPst[0]+crpPst[2], :]  
        dst = img[100:200,300:400,:]
        if self.isDebug:
            cv2.imshow('debug',dst)
            cv2.waitKey(0)
        return dst      


    def imgFilter(self, img, kernelSize=3, times=1):
        I = img
        kernel = np.ones((kernelSize,kernelSize),np.uint8)                      # 定义矩形结构的卷积核
        if type(times) is tuple:
            timesErode = times[0]                                               # 腐蚀次数
            timesDilate = times[1]                                              # 膨胀次数
        else:
            timesErode, timesDilate = times, times
        I = cv2.erode(I,kernel,iterations = timesErode)                         # 腐蚀：用kernel，扫描图像的每一个像素；用kernel与其覆盖的二值图像做 “与” 操作；如果都为1，结果图像的该像素为1；否则为0
        if self.isDebug:
            plt.imshow(I)
            plt.title('debug: Processing imgFilter')
            plt.show()
        I = cv2.dilate(I,kernel,iterations = timesDilate)                       # 膨胀：用kernel，扫描图像的每一个像素；用kernel与其覆盖的二值图像做 “与” 操作；如果都为0，结果图像的该像素为0；否则为1
        if self.isDebug:
            plt.imshow(I)
            plt.title('debug: Processing imgFilter')
            plt.show()

        return I


    def maskImage(self, img, mask):
        if len(img.shape)==3:
            if mask.shape == 3:
                mask = mask[:,:,1]
            maskedImg = img * (np.dstack([mask]*img.shape[2]))                  # np.dstack(tup), 按照第三维方向堆叠
        else:
            maskedImg = img * mask
        return maskedImg

    def resize(self, img, scale=(1,1)):
        dst = cv2.resize(img, None, fx=scale[1], fy=scale[0])

        if self.isDebug:
            cv2.imshow('debug',dst)
            cv2.waitKey(0)

        return dst

   
    def rotateImage(self, img, angle, center=[]):
        rows,cols = img.shape[0:2]
        if center==[]:
            M = cv2.getRotationMatrix2D((cols/2,rows/2),angle,1)
        else:
            M = cv2.getRotationMatrix2D(center,angle,1)
        dst = cv2.warpAffine(img,M,(cols,rows))

        if self.isDebug:
            cv2.imshow('debug',dst)
            cv2.waitKey(0)

        return dst


    def segment(self, img, thresh=0, method='manual',
                adaptiveMethod = 'mean',
                adaptiveBlockSize = 11,
                adaptiveConst = 2):
        I = img.copy()
        if method == 'manual':
            I[I<thresh]=0
            I[I>=thresh]=1
            if self.isDebug:
                plt.imshow(I)
                plt.title('debug: Processing segment')
                plt.show()
        elif method == 'adaptive':                                              # 自动方法（自适应阈值）：适合在不同区域具有不同照明条件的图像。通过规定一个区域大小，比较这个点与区域大小里面
                                                                                # 像素点的平均值（或者其他特征）的大小关系确定这个像素点是属于黑或者白（一般不用）。
            if adaptiveMethod == 'mean':
                m = cv2.ADAPTIVE_THRESH_MEAN_C                                  # ADAPTIVE_THRESH_MEAN_C：领域内均值
            else:
                m = cv2.ADAPTIVE_THRESH_GAUSSIAN_C                              # ADAPTIVE_THRESH_GAUSSIAN_C：领域内像素点加权和，权重为一个高斯窗口

            I = cv2.adaptiveThreshold(I,255,m,                                  # adaptiveThreshold(原始图像，像素值上限，自适应方法，值的赋值方法，规定领域大小，常数C)
                    cv2.THRESH_BINARY,                                          # 只有cv2.THRESH_BINARY(黑白二值) 和cv2.THRESH_BINARY_INV(黑白二值反转)
                    adaptiveBlockSize,                                          # 规定领域大小（显示图像的正方形区域）
                    adaptiveConst)                                              # 阈值等于均值或者加权值减去这个常数（为0相当于阈值 就是求得领域内均值或者加权值）
            if self.isDebug:
                plt.imshow(I)
                plt.title('debug: Processing segment')
                plt.show()
        return I

    def segment_re(self, img, thresh=0, method='manual',
                    adaptiveMethod = 'mean',
                    adaptiveBlockSize = 11,
                    adaptiveConst = 2):
        '''
        mask值反向

        Parameters
        ----------
        img : TYPE
            DESCRIPTION.
        thresh : TYPE, optional
            DESCRIPTION. The default is 0.
        method : TYPE, optional
            DESCRIPTION. The default is 'manual'.
        adaptiveMethod : TYPE, optional
            DESCRIPTION. The default is 'mean'.
        adaptiveBlockSize : TYPE, optional
            DESCRIPTION. The default is 11.
        adaptiveConst : TYPE, optional
            DESCRIPTION. The default is 2.

        Returns
        -------
        I : TYPE
            DESCRIPTION.

        '''
        I = img.copy()
        if method == 'manual':
            I[I<thresh]=-1
            I[I>=thresh]=0
            I[I==-1]=1
            if self.isDebug:
                plt.imshow(I)
                plt.title('debug: Processing segment')
                plt.show()
        elif method == 'adaptive':                                              # 自动方法（自适应阈值）：适合在不同区域具有不同照明条件的图像。通过规定一个区域大小，比较这个点与区域大小里面
                                                                                # 像素点的平均值（或者其他特征）的大小关系确定这个像素点是属于黑或者白（一般不用）。
            if adaptiveMethod == 'mean':
                m = cv2.ADAPTIVE_THRESH_MEAN_C                                  # ADAPTIVE_THRESH_MEAN_C：领域内均值
            else:
                m = cv2.ADAPTIVE_THRESH_GAUSSIAN_C                              # ADAPTIVE_THRESH_GAUSSIAN_C：领域内像素点加权和，权重为一个高斯窗口

            I = cv2.adaptiveThreshold(I,255,m,                                  # adaptiveThreshold(原始图像，像素值上限，自适应方法，值的赋值方法，规定领域大小，常数C)
                    cv2.THRESH_BINARY,                                          # 只有cv2.THRESH_BINARY(黑白二值) 和cv2.THRESH_BINARY_INV(黑白二值反转)
                    adaptiveBlockSize,                                          # 规定领域大小（显示图像的正方形区域）
                    adaptiveConst)                                              # 阈值等于均值或者加权值减去这个常数（为0相当于阈值 就是求得领域内均值或者加权值）
            if self.isDebug:
                plt.imshow(I)
                plt.title('debug: Processing segment')
                plt.show()
        return I
    
    def splitBackgrd(self, img, backgrd, thresh=5000):                          # 背景分割（可能用不到）
        dst = img
        imgDelta = np.abs(np.int32(dst)-np.int32(backgrd))[:,:,1]
        imgDelta = np.uint16(imgDelta)
        if self.isDebug:
            cv2.imshow('debug', imgDelta)
            cv2.waitKey(0)

        imgDelta[imgDelta<thresh] = 0
        imgDelta[imgDelta>=thresh] = 1
        if self.isDebug:
            cv2.imshow('debug', imgDelta*65535)
            cv2.waitKey(0)

        imgseg0 = img*np.dstack([imgDelta]*3)
        if self.isDebug:
            cv2.imshow('debug', imgseg0)
            cv2.waitKey(0)
        dst = imgseg0

        return dst


def imshow(img):
    plt.imshow(img)
    plt.show()

if __name__ == '__main__':
    #%
    img = np.zeros((100,100), dtype=np.uint8)
    plt.xlim(0,80)
    plt.ylim(0,80)
    cv2.circle(img,(30,30), 10, 1, -1)
    cv2.circle(img,(60,60), 10, 1, -1)
    cv2.circle(img,(30,60), 10, 1, -1)
    import matplotlib.pyplot as plt
    plt.imshow(img)
    plt.show()

    contours = Contours.getContours(img)                                        # list[contour_1,...,contour_n]
    img2 = np.zeros((100,100), dtype=np.uint8)
    color = list(range(1, 4))
    img2cnts = Contours.fillContours(img2, contours, color)     #

    plt.imshow(img2cnts)
    plt.show()
    
    features = Contours.getFeatures(contours)

    cnts = Contours.sort(contours, method='leftRight')
    img2 = np.zeros((100,100), dtype=np.uint8)
    color = list(range(1, 4))
    img2cnts = Contours.fillContours(img2, cnts, color)

    prc = Processing()
    img3 = np.random.rand(100,100,3)
    averageValues = prc.averageImgWithMask(img3, mask=img2cnts, flag=color)
    print(averageValues)

    plt.imshow(img2cnts)
    plt.show()

    prc = Processing()
    img3 = np.random.rand(100,100,3)
    img3[img3<0.8] = 0
    averageValues = prc.averageImgWithMask(img3)[0]
    print(averageValues.tolist())

    plt.imshow(img3)
    plt.title('seg no mask')
    plt.show()


